﻿using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;

namespace ZPeng.CppPro.Model.Log
{
    public enum LogLevel
    {
        Info = 0,
        Warning = 1,
        Error = 2,
        Debug
    }

    public struct LogContent
    {
        public LogLevel logLevel;
        public string Time;
        public string Message;
        public LogContent(LogLevel level, string time, string meg)
        {
            logLevel = level;
            Time = time;
            Message = meg;
        }
    }


    public class SimpleLog
    {
        public static string LogFileDir = "Log";

        private string _instanceName;

        private ConcurrentQueue<LogContent> _logQueue;
        //  经过测试使用变量和使用ManualResetEvent类在多线程中标记的效果是一致。 每隔1ms读取一次值，能同时获知值改变。 static ManualResetEvent mre = new ManualResetEvent(false);
        private Task? _task_WriteToFile;
        ManualResetEvent mreIsTaskEnd;

        private static string[] LevelStrList = { "[Info]", "[Warning]", "[Error]", "[Debug]" };


        public string InstanceName { get => _instanceName; set => _instanceName = value; }

        public SimpleLog(string instanceName)
        {
            InstanceName = instanceName;

            mreIsTaskEnd = new ManualResetEvent(true);

            _logQueue = new ConcurrentQueue<LogContent>();
            _task_WriteToFile = null;
        }

        ~SimpleLog()
        {
        }


        public void AddLog(LogLevel level, string message)
        {
            LogContent logContent = new LogContent(level, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), message);
            _logQueue.Enqueue(logContent);

            StartWriteToFile();
        }

        public bool IsWritting()
        {
            return !mreIsTaskEnd.WaitOne(0, false);
        }

        public void WriteForWritten()
        {
            mreIsTaskEnd.WaitOne(-1, true);
        }

        public delegate void LogUpdateHandler(object sender, LogContent log);

        public event LogUpdateHandler? LogUpdateEvent;

        #region 测试方法
        // 手动设置消息日期。仅供测试使用
        public void AddLog_TheTime(LogLevel level, DateTime time, string message)
        {
            LogContent logContent = new LogContent(level, time.ToString("yyyy-MM-dd HH:mm:ss.fff"), message);
            _logQueue.Enqueue(logContent);

            StartWriteToFile();
        }
        #endregion

        #region private

        private void StartWriteToFile()
        {
            // if (_task_WriteToFile == null)
            if (mreIsTaskEnd.WaitOne(0, false))
            {
                mreIsTaskEnd.Reset();

                _task_WriteToFile = new TaskFactory().StartNew(WriteToFileAsync);
            }
        }

        private void WriteToFileAsync()
        {
            if (!Directory.Exists(LogFileDir))
            {
                Directory.CreateDirectory(LogFileDir);
            }
            string time_TheFile = DateTime.Now.ToString("yyyy-MM-dd");
            string fileName = LogFileDir + "\\Log_" + InstanceName + "_" + time_TheFile + ".txt";
            var writer = new StreamWriter(fileName, true);
            LogContent logContent;
            while (_logQueue.TryDequeue(out logContent))
            {
                LogUpdateEvent?.Invoke(this, logContent);

                // 将日志严格写入当天的文件中。
#if DEBUG
                string temp1 = time_TheFile.Substring(8, 2);
                string temp2 = logContent.Time.Substring(8, 2);
#endif
                // 比较日数，就能判断是否是同一天
                if (time_TheFile.Substring(8, 2) != logContent.Time.Substring(8, 2))
                {
                    writer.Close();
                    writer.Dispose();

                    time_TheFile = logContent.Time.Substring(0, 10);
                    fileName = LogFileDir + "\\Log_" + InstanceName + "_" + time_TheFile + ".txt";
                    writer = new StreamWriter(fileName, true);
                }

                writer.WriteLine(LevelStrList[(int)logContent.logLevel] + logContent.Time + ":" + logContent.Message);
            }

            writer.Close();
            writer.Dispose();

            Debug.WriteLine("end write");

            mreIsTaskEnd.Set();
            //_task_WriteToFile = null;
        }

        #endregion
    }
}
